﻿using System;
using System.Buffers;
using System.Runtime.CompilerServices;


namespace Letter.IO.Binary;

public ref struct BytesReader
{
    internal BytesReader(ref ReadOnlySequence<byte> sequence)
    {
        this.readed = 0;
        this.sequence = sequence;
        this.buf = sequence;
        this.Length = sequence.Length;
    }

    private int readed;
    private ReadOnlySequence<byte> buf;
    private ReadOnlySequence<byte> sequence;

    public long Length
    {
        get;
    }

    public long ReadableLength
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get { return this.sequence.Length; }
    }

    public SequencePosition End
    {
        get { return this.buf.GetPosition(this.readed); }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool IsLengthEnough(long length)
    {
        return this.ReadableLength >= length;
    }


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool ReadBoolean() => this.ReadBuffer(1).FReadBoolean();


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public sbyte ReadInt8() => this.ReadBuffer(sizeof(sbyte)).FReadInt8();


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public byte ReadUInt8() => this.ReadBuffer(sizeof(byte)).FReadUInt8();


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public char ReadChar() => this.ReadBuffer(sizeof(char)).FReadChar();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public char ReadCharLE() => this.ReadBuffer(sizeof(char)).FReadCharLE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public char ReadCharBE() => this.ReadBuffer(sizeof(char)).FReadCharBE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public string ReadString()
    {
        var length = this.ReadInt32BE();
        char[] chars = new char[length];
        for (int i = 0; i < length; i++)
        {
            chars[i] = this.ReadChar();
        }

        return new string(chars);
    }


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public short ReadInt16LE() => this.ReadBuffer(sizeof(short)).FReadInt16LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public short ReadInt16BE() => this.ReadBuffer(sizeof(short)).FReadInt16BE();
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ushort ReadUInt16LE() => this.ReadBuffer(sizeof(ushort)).FReadUInt16LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ushort ReadUInt16BE() => this.ReadBuffer(sizeof(ushort)).FReadUInt16BE();
    
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int ReadInt32LE() => this.ReadBuffer(sizeof(int)).FReadInt32LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int ReadInt32BE() => this.ReadBuffer(sizeof(int)).FReadInt32BE();
    
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public uint ReadUInt32LE() => this.ReadBuffer(sizeof(uint)).FReadUInt32LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public uint ReadUInt32BE() => this.ReadBuffer(sizeof(uint)).FReadUInt32BE();
    
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public long ReadInt64LE() => this.ReadBuffer(sizeof(long)).FReadInt64LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public long ReadInt64BE() => this.ReadBuffer(sizeof(long)).FReadInt64BE();

    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ulong ReadUInt64LE() => this.ReadBuffer(sizeof(ulong)).FReadUInt64LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ulong ReadUInt64BE() => this.ReadBuffer(sizeof(ulong)).FReadUInt64BE();
    
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public float ReadFloat32LE() => this.ReadBuffer(sizeof(float)).FReadFloat32LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public float ReadFloat32BE() => this.ReadBuffer(sizeof(float)).FReadFloat32BE();
    
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public double ReadFloat64LE() => this.ReadBuffer(sizeof(double)).FReadFloat64LE();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public double ReadFloat64BE() => this.ReadBuffer(sizeof(double)).FReadFloat64BE();

    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlySequence<byte> ReadBuffer(int length)
    {
        var endPos = this.sequence.GetPosition(length, this.sequence.Start);
        return this.ReadBuffer(endPos);
    }
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlySequence<byte> ReadBuffer(SequencePosition endPosition)
    {
        var segment = this.sequence.Slice(sequence.Start, endPosition);
        this.sequence = this.sequence.Slice(endPosition);
        this.readed += (int)segment.Length;
        return segment;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlySequence<byte> PeekBuffer(int length)
    {
        var endPos = this.sequence.GetPosition(length, this.sequence.Start);
        var segment = this.sequence.Slice(sequence.Start, endPos);
        return segment;
    }
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryFindPosition(byte[] value, out SequencePosition lastPosition)
    {
        if (value == null || value.Length == 0)
        {
            throw new ArgumentNullException(nameof(value));
        }

        int valueLength = value.Length;
        if (valueLength == 1)
        {
            if (this.TryFindPosition(value[valueLength - 1], out lastPosition))
            {
                lastPosition.Offet(1);
            }
        }

        SequencePosition startPosition = this.sequence.Start;
        while (true)
        {
            if (startPosition.GetInteger() >= this.sequence.End.GetInteger())
            {
                break;
            }

            if (this.TryFindPosition(value[valueLength - 1], out var endPos))
            {
                var tempEndPos = endPos.Offet(1);
                var startPos = this.GetStartPosition(ref tempEndPos, valueLength);
                var tempSequence = this.sequence.Slice(startPos, tempEndPos);
                if (this.VerifySequence(ref tempSequence, value))
                {
                    lastPosition = tempEndPos;
                    return true;
                }

                startPosition = tempEndPos;
            }
        }
        
        lastPosition = default;
        return false;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryFindPosition(byte value, out SequencePosition position)
    {
        return TryFindPosition(value, this.sequence.Start, out position);
    }
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool TryFindPosition(byte value, in SequencePosition startPosition, out SequencePosition position)
    {
        return this.sequence.Slice(startPosition, this.sequence.End).TryPositionOf(value, out position);
    }
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private SequencePosition GetStartPosition(ref SequencePosition endPos, int valueLength)
    {
        var endIndex = endPos.GetInteger();
        var startIndex = endIndex - valueLength;
        return new SequencePosition(endPos.GetObject(), startIndex);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool VerifySequence(ref ReadOnlySequence<byte> sequence, byte[] bytes)
    {
        if (sequence.First.Length < bytes.Length)
        {
            Span<byte> segment = stackalloc byte[bytes.Length];
            sequence.CopyTo(segment);
            return VerifyValueEquality(segment, bytes);
        }
        else
        {
            return VerifyValueEquality(sequence.First.GetArray(), bytes);
        }

        bool VerifyValueEquality(in Span<byte> sequenceSpan, byte[] value)
        {
            for (int i = 0; i < sequenceSpan.Length; i++)
            {
                if (sequenceSpan[i] != value[i]) return false;
            }

            return true;
        }
    }

}